<?php

namespace Plugins\AdminBasic\Http\Admin\Service;

use Plugins\AdminBasic\Http\Admin\Service\MakeCURDFiles\MakeEnum;
use Plugins\AdminBasic\Http\Admin\Service\MakeCURDFiles\MakeModel;
use Plugins\AdminBasic\Http\Admin\Service\UpdateCURDFiles\UpdateEnum;
use Plugins\AdminBasic\Http\Admin\Service\UpdateCURDFiles\UpdateModel;
use Hyperf\Stringable\Str;
use Sc\Util\ScTool;
use Sc\Util\Tool;

/**
 * Class UpdateCURDService
 */
class UpdateCURDService
{

    private string $tableStudly;
    private array  $paths = [];
    private array $currentFieldInfo;

    public function __construct(
        private string          $table,
        private string          $tableComment,
        private readonly string $dir,
        private readonly string $plugins,
    )
    {
        $this->table       = DevAuxService::getTable($this->table, false);
        $this->tableStudly = Str::studly($this->table);

        $this->scan();

        $this->currentFieldInfo = $this->currentFieldInfo($this->table);
    }


    public function fullModelClassname(string $shortClassname): string
    {
        $shortClassname = $this->dir ? $this->dir . '\\' . $shortClassname : $shortClassname;

        return $this->plugins
            ? "Plugins\\$this->plugins\\Models\\$shortClassname"
            : "App\\Models\\$shortClassname";
    }

    /**
     * @return void
     * @throws \Exception
     */
    private function scan(): void
    {
        $check = $this->plugins ? MakeCURDService::BASE_PLUGINS_FILE_DIR : MakeCURDService::BASE_FILE_DIR;

        foreach ($check as $mode => $path) {
            $path    = strtr($path, [':plugins' => $this->plugins]);
            $replace = ltrim($this->dir . '/' . $this->tableStudly, '/');

            if ($mode === 'Enum' && is_dir($enumDir = dirname(sprintf($path, $replace)))) {
                ScTool::dir($enumDir)->each(function (Tool\Dir\EachFile $file) {
                    if (str_starts_with($file->filename, $this->tableStudly . "Enum")){
                        $this->paths['Enum:' . strtr($file->filename, [
                            $this->tableStudly . "Enum" => '',
                            '.php' => ''
                        ])] = $file->filepath;
                    }
                });
                continue;
            }
            if ($mode != 'View') {
                $this->paths[$mode] = sprintf($path, $replace);
                continue;
            }
            $path = sprintf($path, $replace . "/%s");
            foreach (['lists', 'create', 'edit'] as $view){
                if (is_file(sprintf($path, $view))) {
                    $this->paths['View:' . $view] = sprintf($path, $view);
                }
            }
        }
    }

    private function modelCodeUpdate(bool $isView = true): bool|string
    {
        $updateModel = new UpdateModel($this->paths['Model'], $this->fullModelClassname($this->tableStudly), $this->currentFieldInfo);

        return $updateModel->update($isView);
    }

    private function enumCodeUpdate()
    {
        $translation = MakeCURDService::statusMapTranslation($this->currentFieldInfo);
        $res = [];
        foreach ($this->currentFieldInfo as $currentInfo) {
            if (empty($currentInfo['status_map']) || MakeEnum::fieldIsBoolean($currentInfo['status_map'])) {
                continue;
            }

            $enumFullClassname = strtr($this->fullModelClassname($currentInfo['field_type']), ['\\Models' => '\\Enums']);

            $enumPath = dirname(strtr($this->paths['Model'], ['Models' => 'Enums'])) . DIRECTORY_SEPARATOR . $currentInfo['field_type'] . ".php";
            $updateEnum = new UpdateEnum(
                $enumFullClassname,
                $enumPath,
                $currentInfo,
                $translation
            );

            $res[] = $updateEnum->update($this->tableComment);
        }

        return implode("\n", $res);
    }

    /**
     * @param mixed $table
     * @return array
     */
    public function getFields(mixed $table): array
    {
        $fields = DevAuxService::aop()->getFields($table);
        $fields = json_decode(json_encode($fields), true);
        foreach ($fields as &$field) {
            $field['field'] = $field['COLUMN_NAME'];
            $field['field_type'] = $field['COLUMN_TYPE'];
            $field['comment'] = $field['COLUMN_COMMENT'];
        }
        unset($field);
        return $fields;
    }

    /**
     * @param mixed $table
     * @return array
     */
    public function currentFieldInfo(mixed $table): array
    {
        $fields = $this->getFields($table);
        foreach ($fields as &$field) {
            $field['comment'] = strtr($field['comment'], ['：' => ':', '，' => ',']);
            if (!str_contains($field['comment'], ':') || !str_contains($field['comment'], '=')) {
                continue;
            }
            [$field['comment'], $maps] = explode(':', $field['comment']);
            $field['comment'] = trim($field['comment']);
            $field['status_map'] = array_map(function ($item) {
                $map = explode('=', $item);
                return ['key' => trim($map[0]), 'value' => trim($map[1])];
            }, explode(',', $maps));
        }
        unset($field);

        $commentByFields = MakeModel::getCommentByFields($fields, $table);
        $fields = array_column($fields, null, 'field');
        foreach ($commentByFields as &$field) {
            if (!preg_match('/@property\s+(\w+)\s+\$(\w+) (.*)$/m', $field, $info)) {
                continue;
            }
            $field = [
                'field' => $info[2],
                'field_type' => $info[1],
                'column_type' => $fields[$info[2]]['field_type'],
                'comment' => $info[3],
                'status_map' => $fields[$info[2]]['status_map'] ?? [],
                'doc' => $field,
            ];
        }

        return array_column($commentByFields, null, 'field');
    }

    public function check(): string
    {
        return $this->modelCodeUpdate();
    }

    /**
     * @return string
     */
    public function exec(): string
    {
        $modelMessage = $this->modelCodeUpdate(false);
        $message = $this->enumCodeUpdate();

        return $modelMessage . "\n" . $message;
    }
}